tests: Run all tests through a randomized readdir()
authorColin Walters <walters@verbum.org>
Tue, 2 Jun 2015 02:34:14 +0000 (22:34 -0400)
committerColin Walters <walters@verbum.org>
Tue, 2 Jun 2015 02:34:14 +0000 (22:34 -0400)
Having undefined ordering (but in practice rarely changing)
ordering for `readdir()` ended up screwing us over with respect
to bootloader config file read ordering.

Let's make things significantly more likely to fail more quickly in
the future if similar bugs are introduced.  We accomplish this by
introducing a little `LD_PRELOAD` library that randomizes the results
of `readdir()`.

Makefile-tests.am
tests/libtest.sh
tests/readdir-rand.c [new file with mode: 0644]

index 955cef177dbd1fcfb3248c027f2191fecf68a6a4..68dcb9b048fbff59962a6879467e72f898f8ff74 100644 (file)
@@ -113,6 +113,12 @@ insttest_SCRIPTS += tests/test-core.js \
 testmeta_DATA += test-core.test test-sizes.test test-sysroot.test
 endif
 
+insttest_LTLIBRARIES = libreaddir-rand.la
+libreaddir_rand_la_SOURCES = tests/readdir-rand.c
+libreaddir_rand_la_CFLAGS = $(OT_INTERNAL_GIO_UNIX_CFLAGS)
+libreaddir_rand_la_LIBADD = $(OT_INTERNAL_GIO_UNIX_LIBS)
+libreaddir_rand_la_LDFLAGS = -avoid-version
+
 endif
 
 # "make check" do not depend from --enable-installed-tests
index ba435c092713dcf5cab424d60604122fac773cdd..b788bc979cad3143f802c3742997f620976af2c0 100644 (file)
@@ -39,6 +39,8 @@ fi
 
 if test -n "$OT_TESTS_VALGRIND"; then
     CMD_PREFIX="env G_SLICE=always-malloc valgrind -q --leak-check=full --num-callers=30 --suppressions=${SRCDIR}/ostree-valgrind.supp"
+else
+    CMD_PREFIX="env LD_PRELOAD=${SRCDIR}/libreaddir-rand.so"
 fi
 
 assert_not_reached () {
diff --git a/tests/readdir-rand.c b/tests/readdir-rand.c
new file mode 100644 (file)
index 0000000..70a001f
--- /dev/null
@@ -0,0 +1,124 @@
+#define _GNU_SOURCE
+#include <dirent.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <glib.h>
+
+struct linux_dirent {
+  long           d_ino;
+  off_t          d_off;
+  unsigned short d_reclen;
+  char           d_name[];
+};
+
+#define BUF_SIZE 1024
+
+static GHashTable *direntcache;
+static GMutex direntcache_lock;
+static gsize initialized;
+
+typedef struct {
+  GPtrArray *entries;
+  guint offset;
+} DirEntries;
+
+static void
+dir_entries_free (gpointer data)
+{
+  DirEntries *d = data;
+  g_ptr_array_unref (d->entries);
+  g_free (d);
+}
+
+static DirEntries *
+dir_entries_new (void)
+{
+  DirEntries *d = g_new0 (DirEntries, 1);
+  d->entries = g_ptr_array_new_with_free_func (g_free);
+  return d;
+}
+
+static void
+ensure_initialized (void)
+{
+  if (g_once_init_enter (&initialized))
+    {
+      direntcache = g_hash_table_new_full (NULL, NULL, NULL, dir_entries_free);
+      g_mutex_init (&direntcache_lock);
+      g_once_init_leave (&initialized, 1);
+    }
+}
+
+struct dirent *
+readdir (DIR *dirp)
+{
+  struct dirent *(*real_readdir)(DIR *dirp) = dlsym (RTLD_NEXT, "readdir");
+  struct dirent *ret;
+  gboolean doloop = TRUE;
+  
+  ensure_initialized ();
+
+  while (doloop)
+    {
+      DirEntries *de;
+      GSList *l;
+
+      errno = 0;
+      ret = real_readdir (dirp);
+      if (ret == NULL && errno != 0)
+       goto out;
+
+      g_mutex_lock (&direntcache_lock);
+      de = g_hash_table_lookup (direntcache, dirp);
+      if (ret)
+       {
+         if (g_random_boolean ())
+           {
+             if (!de)
+               {
+                 de = dir_entries_new ();
+                 g_hash_table_insert (direntcache, dirp, de);
+               }
+             struct dirent *copy;
+             copy = g_memdup (ret, sizeof (struct dirent));
+             g_ptr_array_add (de->entries, copy);
+           }
+         else
+           {
+             doloop = FALSE;
+           }
+       }
+      else
+       {
+         if (de && de->offset < de->entries->len)
+           {
+             ret = de->entries->pdata[de->offset];
+             de->offset++;
+           }
+         doloop = FALSE;
+       }
+      g_mutex_unlock (&direntcache_lock);
+    }
+  
+ out:
+  return ret;
+}
+
+int
+closedir (DIR *dirp)
+{
+  int (*real_closedir)(DIR *dirp) = dlsym (RTLD_NEXT, "closedir");
+
+  ensure_initialized ();
+  
+  g_mutex_lock (&direntcache_lock);
+  g_hash_table_remove (direntcache, dirp);
+  g_mutex_unlock (&direntcache_lock);
+
+  return real_closedir (dirp);
+}